package ro.romanescu.loc4j.xliff;

import java.net.URL;

import java.util.HashMap;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.codehaus.stax2.XMLInputFactory2;
import org.codehaus.stax2.XMLStreamReader2;

import ro.romanescu.loc4j.Dictionary;
import ro.romanescu.loc4j.LocalizationException;

/**
 * Implementation for the &lt;xliff/&gt; tag for the XLIFF OASIS specification.
 * An xliff tag contains the whole translation for one language.
 */
public class Xliff extends HashMap<String, TransUnit> implements Dictionary {
  private static final Log log = LogFactory.getLog( Xliff.class );
  private URL xliff = null;
  private String languageISOCode = null;
  private String languageName = null;
  private String languageNameNative = null;
  private boolean loaded = false;
  private boolean lazyLoading = true;


  /**
   * Load the xliff file.
   */
  private void load() {
    loaded = true;
    log.debug( "Loading dictionary for this XLIFF" );
    XMLInputFactory2 xmlif = null;
    try {
      xmlif = ( XMLInputFactory2 )XMLInputFactory.newInstance();
      xmlif.setProperty( XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE );
      xmlif.setProperty( XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE );
      xmlif.setProperty( XMLInputFactory.IS_COALESCING, Boolean.FALSE );
      xmlif.configureForSpeed();

      XMLStreamReader2 xmlr = xmlif.createXMLStreamReader( xliff );

      int evtType;
      String currentElement;
      while( xmlr.next() != XMLEvent.END_DOCUMENT ) {
        evtType = xmlr.getEventType();
        switch( evtType ) {
          case XMLEvent.START_ELEMENT:
            currentElement = xmlr.getName().toString();
            if( currentElement.equals( "trans-unit" ) ) {
              TransUnit tu = getTransUnit( xmlr, evtType, currentElement );
              if( tu != null ) {
                put( tu.getId(), tu );
              } else {
                log.warn( "Skipped null TransUnit" );
              }
            }
        }
      }
    } catch( XMLStreamException ex ) {
      log.error( "Error while loading dictionary from XLIFF file", ex );
    } catch( LocalizationException ex ) {
      log.error( "Error while loading dictionary from XLIFF file", ex );
    }
  }


  /**
   * Retrieve one &lt;trans-unit/&gt; contents.
   * @param xmlr XML Stream reader
   * @param evtType Type of event where parsing stopped in previous step
   * @param currentElement Current element in previous step
   * @return Trans unit read
   * @throws LocalizationException While parsing the XML stream
   */
  private TransUnit getTransUnit( XMLStreamReader2 xmlr, int evtType, 
                                  String currentElement ) throws LocalizationException {
    TransUnit tu = null;
    currentElement = "";
    int attrCount = xmlr.getAttributeCount();
    for( int i = 0; i < attrCount; i++ ) {
      String attrName = xmlr.getAttributeName( i ).toString();
      String attrVal = xmlr.getAttributeValue( i );
      if( attrName.equals( "id" ) ) {
        tu = new TransUnit();
        tu.setId( attrVal );
      }
    }
    try {
      // Read until </trans-unit>
      while( !( currentElement.equals( "trans-unit" ) && evtType == XMLEvent.END_ELEMENT ) ) {
        xmlr.next();
        evtType = xmlr.getEventType();
        switch( evtType ) {
          case XMLEvent.START_ELEMENT:
            currentElement = xmlr.getName().toString();
            break;
          case XMLEvent.END_ELEMENT:
            currentElement = xmlr.getName().toString();
            break;
          case XMLEvent.CHARACTERS:
            if( currentElement.equals( "source" ) ) {
              if( tu == null )
                tu = new TransUnit();
              tu.setSource( xmlr.getText() );
            }
            if( currentElement.equals( "target" ) ) {
              if( tu == null )
                tu = new TransUnit();
              tu.setTarget( xmlr.getText() );
            }
        }
      }
    } catch( XMLStreamException ex ) {
      throw new LocalizationException( ex );
    }
    return tu;
  }


  /**
   * Language ISO encoding. For example "ro_RO" or "en_US" etc.
   * @return language unique ISO code
   */
  public String getLanguageISOCode() {
    return languageISOCode;
  }


  /**
   * Language name in english.
   * @return Language name written in english language
   */
  public String getLanguageName() {
    return languageName;
  }


  /**
   * Language name in native language of the dictionary.
   * @return Language name written in native language
   */
  public String getLanguageNameNative() {
    return languageNameNative;
  }


  /**
   * Retrieve one value from the dictionary.
   * @param key Key for the specified value
   * @return String correspondent of the specified key
   */
  public String getString( String key ) {
    if( !loaded ) {
      log.info( "Lazily loading dictionary from XLIFF file" );
      load();
    }
    if( containsKey( key ) ) {
      TransUnit tu = get( key );
      return tu.getTarget();
    }
    log.warn( String.format( "Missing key %s", key ) );
    return key;
  }


  /**
   * Setter for property isoCode.
   * @param languageISOCode ISO code for the language within dictionary
   */
  public void setLanguageISOCode( String languageISOCode ) {
    this.languageISOCode = languageISOCode;
  }


  /**
   * Setter for language name.
   * @param languageName Language name in english language
   */
  public void setLanguageName( String languageName ) {
    this.languageName = languageName;
  }


  /**
   * Setter for language name.
   * @param languageNameNative Language name in native language of the dictionary.
   */
  public void setLanguageNameNative( String languageNameNative ) {
    this.languageNameNative = languageNameNative;
  }


  /**
   * Set the path to the XLIFF file.
   * @param xliff URL path to the XLIFF file
   */
  public void setXliff( URL xliff ) {
    this.xliff = xliff;
    if( !lazyLoading && !loaded ) {
      load();
    }
  }


  /**
   * Retrieve the path to the XLIFF file.
   * @return Path as URL
   */
  public URL getXliff() {
    return xliff;
  }


  /**
   * String representation of the object.
   * @return Informations about the dictionary in human language
   */
  public String toString() {
    return String.format( "Dictionary ISO: %s, language: %s - location: %s", languageISOCode, languageName, xliff );
  }


  /**
   * Dictionary is loaded.
   * @return true if dictionary has been loaded
   */
  public boolean isLoaded() {
    return loaded;
  }


  /**
   * Retrieve the lazy loading flag.
   * @return true if dictionary is lazy loading itself
   */
  public boolean isLazyLoading() {
    return lazyLoading;
  }


  /**
   * Configure the dictionary to lazy load itself (true) or not (false).
   * @param lazyLoading Lazy initialization strategy
   * @throws LocalizationException If dictionary has already been loaded
   */
  public void setLazyLoading( boolean lazyLoading ) throws LocalizationException {
    if( loaded ) 
      throw new LocalizationException( "Cannot set lazy loading after dictionary has been loaded" );
    this.lazyLoading = lazyLoading;
  }


  /**
   * Retrieve the translation unit associated with that key.
   * A translation unit keeps more than the value associated, according to
   * OASIS XLIFF specification.
   * @param key Key for the specified translation unit
   * @return Translation unit for that key or null if no associated value exists
   */
  public TransUnit getTranslationUnit( String key ) {
    if( containsKey( key ) ) {
      return get( key );  
    }
    log.warn( String.format( "Missing translation unit for key %s", key ) );
    return null;
  }
}
